package nl.scalasim

import scala.actors.{Futures, Actor}
import nl.scalasim.actors.traits.SimulationActor
import nl.scalasim.controlactors.{Guildmaster, Historian, Choreographer, Governor}

/**
 * Main entry point for managing scalasim simulations.
 *
 * Scalasim simulations contain two kinds of actors: simulation
 * actors are active participants that form a visible part of the game.
 * Control actors are not a visible part of the game, but ensure that
 * all simulation actors can do what they should do.  
 *
 * @author Yigal Duppen
 * @since Oct 1, 2010
 */
class Controller(val setting: Setting) extends AnyRef
        with Logging
{

  val addressBook = AddressBook(new Governor(), new Choreographer(new TileMap()), new Historian(), new Guildmaster())

  private var running = false

  private val citizenDescriptor = setting.types find {_.name == "Citizen"} get

  /**
   * All running actors within this simulation. 
   */
  private var _actors = Set[SimulationActor]()

  /**
   * The set of simulation actors in the simulation.
   */
  def actors: Set[SimulationActor] = _actors

  /**
   * Check whether an actor based on <code>d</code> can be added
   * to the simulation on the specified position. 
   */
  def canAdd(d: ActorDescriptor, pos: Position): Boolean = {
    // todo: use capabilities
    if (d.name == "Citizen") {
      true
    }
    else {
      addressBook.choreographer !? (100, CanAdd(d, pos)) match {
        case None => {
          log.severe("Choreographer seems dead")
          false
        }
        case Some(false) => false
        case Some(true) => true
      }
    }
  }

  def add(d: ActorDescriptor, pos: Position): Option[SimulationActor] = {
    if (!canAdd(d, pos)) error("Cannot build " + d + " at " + pos)
    setting.newActor(d, addressBook) match {
      case None => None
      case Some(a) => {
        _actors += a
        if (running) a.start()
        val responses = addressBook.broadcast(ActorAdded(a, pos))
        Futures.awaitAll(100, responses.toSeq: _*)
        Some(a)
      }
    }
  }

  /**
   * Start the simulation.
   */
  def start() {
    running = true

    addressBook.all foreach {_.start()}
    actors foreach {_.start()}
  }

  /**
   * Advance the simulation by one step. When this method finishes,
   * all actors in the simulation are guaranteed to have finished their 
   * activities
   *
   * @param timeout maximum amount of time to run non-control actors
   */
  def tick(timeout: Int = 1000) {
    log.info("New turn...")

    // first, run all the control actors, as we want to do stuff with their
    // responses
    val controlResponses = addressBook.broadcast(Act)
    Futures.awaitAll(timeout, controlResponses.toSeq: _*)

    controlResponses foreach {
      _() match {
        case AddCitizens(n) => (1 to n) foreach {_ => add(citizenDescriptor, (0, 0))}
        case Ack => ()
        case m@_ => log.warning("Actor said: " + m)
      }
    }

    // and now we run all simulation actors 
    val futures = actors map {_ !! Act}

    Futures.awaitAll(timeout, futures.toSeq: _*)

    futures foreach {
      _() match {
        case Ack => ()
        case m@_ => {
          log.warning("Unknown message: " + m)
        }
      }
    }
  }

  /**
   * Stop the simulation. When this method finished, either all
   * actors in the simulation have been stopped, or a timeout
   * has occurred.
   */
  def quit(timeout: Int = 1000) {
    val all: Set[Actor] = actors ++ addressBook.all
    all foreach {
      _ ! Stop
    }

    def allStopped = {
      (all find {
        _.getState != Actor.State.Terminated
      }) == None
    }

    val endTime = System.currentTimeMillis + timeout
    while ((System.currentTimeMillis < endTime) && !allStopped) {
      Thread.sleep(1)
    }

    if (!allStopped) {
      log.warning("Not all actors were stopped")
      all filter {_.getState != Actor.State.Terminated} foreach {a => log.warning(a + " " + a.getState)}
      error("Not all actors were stopped")
    }
  }

}